/**
 * 模拟Commonjs
 * 
 * 
 */

class Module {
  constructor(moduleName, source) {
    this.exports = {}
    this.moduleName = moduleName
    // 缓存
    this.$cacheModule = new Map()
    // 源代码
    this.$source = source
  }
  /**
   * require
   * @param {string} moduleName 模块名称其实就是路径信息
   * @param {string} source 文件的字符串代码
   * @returns {object} 返回的结果就是exports的引用
   */
  require = (moduleName, source) => {
    // 判断是否有缓存 有缓存则直接返回 
    if (this.$cacheModule.has(moduleName)) {
      // 注意返回的是exports 这里头的内容
      return this.$cacheModule.get(moduleName).exports
    }
    // 创建模块
    const module = new Module(moduleName, source)
    //执行文件内容
    const exports =  this.compile(module,source)

    // 设置缓存
    this.$cacheModule.set(moduleName,module)

    // 返回 exports
    return exports
  }

  /**
   * 
   * @param {string} code 代码字符串 
   */

  $wrap = (code)=>{
    const wrapper = [
      'return (function (module,exports,require){',
      '\n});'
    ]
    return wrapper[0]+code+wrapper[1]
  }

  /**
   * 
   * 简单实现一个能在浏览器跑的解释器 runInThisContext 
   * 创建一个隔离的沙箱环境 来执行我们的代码字符串
   *  
   * 隔离：不能访问闭包变量，不能访问全局变量， 只能访问我们代入的变量
   * @param {string} code 
   * 
   * eval:可以访问全局/闭包，但是需要解释执行
   * new Function: 不可以访问闭包 可以访问全局， 只编译一次
   * with: with 包裹的对象，会被放到原型链的顶部，而且底层是通过in操作符判断的
   *       如果通过with塞入我们传入的数据
   *       不论是啥属性， 都从我们塞入的对象取值，取不到就返回undefined，这样就永远不会访问全局的域了
   * 
   * unscopable: 这个对象是不能够被with处理的
   * 
   */

  $runInThisContext=(code,whiteList=['console'])=>{
    // 使用with保证可以通过我们传入的 sendbox 对象获取数据
    // new Funtion不能访问闭包
    const func = new Function('sendbox',`with(sendbox){${code}}`)
    return function (sendbox){
      if (!sendbox || typeof sendbox !== 'object') {
        throw Error('sendbox parameter must be an object.')
      }
      // 代理
      const proxiedObject = new Proxy(sendbox,{
        // 专门处理 in 操作符
        has (target,key){
          if (!whiteList.includes(key)) {
            return true
          }
        },
        get(target,key,receiver){
          if (key === Symbol.unscopables) {
            return void 0;
          }
          return Reflect.get(target,key,receiver)
        }
      })

      return func(proxiedObject)
    }
  }

  /**
   * 执行文件内容
   * 
   */
  compile = (module, source) => { 
    // todo
    const iifeString = this.$wrap(source)
    // 创建沙箱执行环境
    const compiler = this.$runInThisContext(iifeString)({})
    compiler.call(module,module,module.exports,this.require)
    console.log(module)
    return module.exports
  }
}

/**
 * demo验证
 */
const m = new Module()

//a.js
const sourceCodeFromModule = `
  const b = require('./b.js','exports.action = function(){console.log("this is b module")}')
  console.log("this is a module")
  b.action()
`
console.log('m==== ',m)

m.require('a.js',sourceCodeFromModule)

var a = {
  i: 0,
  [Symbol.toPrimitive](){
    return ++this.i
  }
}

if (a == 1 && a == 2 && a == 3 ) {
  console.log('true')
}